package com.rizzo.web.util;

import com.rizzo.back.exception.ApplicationException;
import com.rizzo.back.exception.ApplicationExceptionItem;
import com.rizzo.web.util.domain.ContentTypeEnum;
import com.rizzo.web.util.domain.FileEncodingEnum;
import org.apache.commons.io.IOUtils;

import javax.faces.context.FacesContext;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.*;

/**
 * Utility that provides various methods for streaming data to the
 * HttpServletResponse.
 *
 * @author Jan Lievens
 */
public class StreamUtil {

    /**
     * Private constructor because it's a util class.
     */
    private StreamUtil() {
    }

    /**
     * Streams a {@link StringBuffer} onto the
     * {@link javax.servlet.http.HttpServletResponse}. Can only be used inside
     * a actionListener!
     *
     * @param stringBuffer     The content in StringBuffer-form.
     * @param completeFileName The filename of the file that is to be streamed.
     * @param encoding         The encoding of the stream.
     * @throws ApplicationException
     */
    public static void streamFileToResponse(StringBuffer stringBuffer,
                                            String completeFileName, FileEncodingEnum encoding)
            throws ApplicationException {
        try {
            FacesContext context = FacesContext.getCurrentInstance();
            HttpServletResponse httpServletResponse = setResponseHeaderInfo(
                    stringBuffer.length(), completeFileName, encoding);
            ServletOutputStream servletOutputStream = httpServletResponse
                    .getOutputStream();
            OutputStreamWriter outputStreamWriter = new OutputStreamWriter(
                    servletOutputStream, encoding.getFileEncoding());
            PrintWriter printWriter = new PrintWriter(outputStreamWriter);
            printWriter.write(stringBuffer.toString());
            printWriter.flush();
            context.responseComplete();
        } catch (IOException exception) {
            throw new ApplicationException(
                    new ApplicationExceptionItem(
                            "Fout tijdens het streamen van filename: "
                                    + completeFileName
                                    + " naar de HttpServletResponse",
                            MessageUtil.getMessage("stream.error",
                                    completeFileName)));
        }
    }

    /**
     * Streams a {@link byte[]} onto the
     * {@link javax.servlet.http.HttpServletResponse}. Can only be used inside
     * a actionListener!
     *
     * @param content          The byte-array with the content to be streamed.
     * @param completefileName The filename of the file that is to be streamed.
     * @throws ApplicationException
     */
    public static void streamBytesToResponse(byte[] content,
                                             String completefileName) throws ApplicationException {
        try {
            FacesContext context = FacesContext.getCurrentInstance();
            HttpServletResponse httpServletResponse = setResponseHeaderInfo(
                    content.length, completefileName, null);

            ServletOutputStream servletOutputStream = httpServletResponse
                    .getOutputStream();
            BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(
                    servletOutputStream);
            bufferedOutputStream.write(content);
            bufferedOutputStream.flush();
            context.responseComplete();
        } catch (IOException exception) {
            throw new ApplicationException(
                    new ApplicationExceptionItem(
                            "Fout tijdens het streamen van filename: "
                                    + completefileName
                                    + " naar de HttpServletResponse",
                            MessageUtil.getMessage("stream.error",
                                    completefileName)));
        }
    }

    /**
     * Streams a {@link byte[]} onto the
     * {@link javax.servlet.http.HttpServletResponse}. Can only be used inside
     * a actionListener!
     *
     * @param stream   pdf stream
     * @param pdfSize  size of pdf.
     * @param filename The filename of the file that is to be streamed.
     * @throws ApplicationException
     */
    public static void streamPdfToResponse(InputStream stream, int pdfSize,
                                           String filename) throws ApplicationException {
        streamToResponse(stream, pdfSize, filename + ".pdf");
    }

    /**
     * Streams a {@link byte[]} onto the
     * {@link javax.servlet.http.HttpServletResponse}. Can only be used inside
     * a actionListener!
     *
     * @param content          The byte-array with the content to be streamed.
     * @param contentLength    The length
     * @param completefileName The filename of the file that is to be streamed.
     * @throws ApplicationException
     */
    public static void streamToResponse(InputStream content, int contentLength,
                                        String completefileName) throws ApplicationException {
        try {
            FacesContext context = FacesContext.getCurrentInstance();
            HttpServletResponse httpServletResponse = setResponseHeaderInfo(
                    contentLength, completefileName, null);
            ServletOutputStream servletOutputStream = httpServletResponse
                    .getOutputStream();
            IOUtils.copy(content, servletOutputStream);
            servletOutputStream.flush();
            context.responseComplete();
        } catch (IOException exception) {
            throw new ApplicationException(
                    new ApplicationExceptionItem(
                            "Fout tijdens het streamen van filename: "
                                    + completefileName
                                    + " naar de HttpServletResponse",
                            MessageUtil.getMessage("stream.error",
                                    completefileName)));
        }
    }

    /**
     * Streams a {@link char[]} onto the
     * {@link javax.servlet.http.HttpServletResponse}. Can only be used inside
     * a actionListener!
     *
     * @param content          The char-array with the content to be streamed.
     * @param completefileName The filename of the file that is to be streamed.
     * @param encoding         The file encoding
     * @throws ApplicationException
     */
    public static void streamCharsToResponse(char[] content,
                                             String completefileName, FileEncodingEnum encoding)
            throws ApplicationException {
        try {
            FacesContext context = FacesContext.getCurrentInstance();
            HttpServletResponse httpServletResponse = setResponseHeaderInfo(
                    content.length, completefileName, encoding);

            ServletOutputStream servletOutputStream = httpServletResponse
                    .getOutputStream();
            OutputStreamWriter outputStreamWriter = new OutputStreamWriter(
                    servletOutputStream, encoding.getFileEncoding());
            PrintWriter printWriter = new PrintWriter(outputStreamWriter);
            printWriter.write(content);
            printWriter.flush();
            printWriter.close();
            context.responseComplete();
        } catch (IOException exception) {
            throw new ApplicationException(
                    new ApplicationExceptionItem(
                            "Fout tijdens het streamen van filename: "
                                    + completefileName
                                    + " naar de HttpServletResponse",
                            "stream.error", completefileName));
        }
    }

    /**
     * Private method that sets all the necessary information in the
     * response-header.
     *
     * @param length           The length of the content in bytes.
     * @param completefileName The filename of the file that is to be streamed.
     * @param encoding         The encoding
     * @return The configured {@link javax.servlet.http.HttpServletResponse}.
     */
    private static HttpServletResponse setResponseHeaderInfo(Integer length,
                                                             String completefileName, FileEncodingEnum encoding) {
        HttpServletResponse httpServletResponse = (HttpServletResponse) FacesContext
                .getCurrentInstance().getExternalContext().getResponse();
        httpServletResponse.setHeader("pragma", "public");
        httpServletResponse.setHeader("Content-Disposition",
                "attachment;filename=" + completefileName.replace(' ', '_'));
        httpServletResponse.setHeader("Cache-Control", "public");
        httpServletResponse.setIntHeader("Content-length", length);
        if (encoding != null) {
            httpServletResponse.setHeader("Content-Encoding", encoding
                    .getFileEncoding());
            httpServletResponse
                    .setCharacterEncoding(encoding.getFileEncoding());
        }
        httpServletResponse.setContentType(ContentTypeEnum.resolveContentType(
                completefileName).getContentType());
        return httpServletResponse;
    }
}